;(function($) {
if ($.fn.viewport) {
	return;
}

$.fn.viewport = function(settings) {
	var defaults = {
		targets: "",
		threshold: 0,
		// this will effect the detail of {top, bottom, left, right} detecting
		thresholdBorderReaching: 0,
		delay: 500,
		onViewChange: null
	};

	// overwrite the default options
	var opts = $.extend(defaults, settings);
	var percent = /^0\.[0-9]+$/.test(opts.thresholdBorderReaching.toString());
	if (!percent) {
		opts.thresholdBorderReaching = Math.max(parseInt(opts.thresholdBorderReaching), 0);
	}

	// jQuery doesn't support a is string judgement, so I made it by myself.
	function isString(obj) {
		return typeof obj == "string" || Object.prototype.toString.call(obj) === "[object String]";
	}


	// patch the container if it is the window object.
	function patchContainer(container) {
		return container === window ?
				(document.compatMode == "CSS1Compat" ? document.documentElement : document.body) : container;
	}


	// detecting the element below the container's viewport or not.
	function belowTheView(element, container) {
		var viewBottom, elementTop;
		var $element = $(element);
		var $container = $(container), $patchContainer = $(patchContainer(container));

		if (container === window) {
			viewBottom = $container.height() + $container.scrollTop();
		} else {
			viewBottom = $container.height() + $container.offset().top;
		}

		viewBottom += (parseInt($patchContainer.css("border-top-width")) || 0) +
					  (parseInt($patchContainer.css("padding-top")) || 0) +
					  (parseInt($patchContainer.css("padding-bottom")) || 0);
		elementTop = $element.offset().top - opts.threshold +
					(parseInt($element.css("padding-top")) || 0) +
					(parseInt($element.css("border-top-width")) || 0);

		return parseInt(viewBottom) <= parseInt(elementTop);
	};


	// detecting the element right the container's viewport or not.
	function rightTheView(element, container) {
		var viewRight, elementLeft;
		var $element = $(element);
		var $container = $(container), $patchContainer = $(patchContainer(container));

		if (container === window) {
			viewRight = $container.width() + $container.scrollLeft();
		} else {
			viewRight = $container.width() + $container.offset().left;
		}

		viewRight += (parseInt($patchContainer.css("border-left-width")) || 0) +
					 (parseInt($patchContainer.css("padding-left")) || 0) +
					 (parseInt($patchContainer.css("padding-right")) || 0);
		elementLeft = $element.offset().left - opts.threshold +
					 (parseInt($element.css("padding-left")) || 0) +
					 (parseInt($element.css("border-left-width")) || 0);

		return parseInt(viewRight) <= parseInt(elementLeft);
	};


	// detecting the element above the container's viewport or not.
	function aboveTheView(element, container) {
		var viewTop, elementBottom;
		var $element = $(element);
		var $container = $(container), $patchContainer = $(patchContainer(container));

		if (container === window) {
			viewTop = $container.scrollTop();
		} else {
			viewTop = $container.offset().top;
		}

		viewTop += (parseInt($patchContainer.css("padding-top")) || 0) +
					(parseInt($patchContainer.css("border-top-width")) || 0);
		elementBottom = $element.offset().top +  $element.height() + opts.threshold +
						(parseInt($element.css("padding-top")) || 0) +
						(parseInt($element.css("padding-bottom")) || 0) +
						(parseInt($element.css("border-top-width")) || 0) +
						(parseInt($element.css("border-bottom-width")) || 0);

		return parseInt(viewTop) >= parseInt(elementBottom);
	};


	// detecting the element left the container's viewport or not.
	function leftTheView(element, container) {
		var viewLeft, elementRight;
		var $element = $(element);
		var $container = $(container), $patchContainer = $(patchContainer(container));

		if (container === window) {
			viewLeft = $container.scrollLeft();
		} else {
			viewLeft = $container.offset().left;
		}

		viewLeft += (parseInt($patchContainer.css("padding-left")) || 0) +
					(parseInt($patchContainer.css("border-left-width")) || 0);
		elementRight = $element.offset().left +  $element.width() + opts.threshold +
					  (parseInt($element.css("padding-left")) || 0) +
					  (parseInt($element.css("padding-right")) || 0) +
					  (parseInt($element.css("border-left-width")) || 0) +
					  (parseInt($element.css("border-right-width")) || 0);

		return parseInt(viewLeft) >= parseInt(elementRight);
	};


	// detecting the element inside the container's viewport or not.
	function insideTheView(element, container) {
		return !belowTheView(element, container) && !rightTheView(element, container) &&
				!aboveTheView(element, container) && !leftTheView(element, container);
	}


	// filter the dom elements which are inside the container's viewport.
	function filterInsideView($elements, container) {
		// do half-search to get a inside view element
		var left = 0, right = $elements.length - 1, center, flag;
		while (left <= right) {
			center = Math.floor((left + right) / 2);

			if (insideTheView($elements[center], container)) {
				flag = true;
				break;
			}

			if (belowTheView($elements[center], container) ||
				rightTheView($elements[center], container)) {
				right = center - 1;
			} else {
				left = center + 1;
			}
		}

		// get others inside view elements around the "ceneter" element.
		var i, element, result = [];
		if (flag) {
			// forward search others
			i = 1;
			while(center - i >= 0) {
				element = $elements[center - i];
				if (insideTheView(element, container)) {
					result.push(element);
				}
				else {
					break;
				}
				
				i++;
			}
			result.reverse();
			result.push($elements[center]);

			// backward search others
			i = 1;
			while(center + i <= $elements.length - 1) {
				element = $elements[center + i];
				if (insideTheView(element, container)) {
					result.push(element);
				}
				else {
					break;
				}

				i++;
			}
		}

		return result;
	}


	// trigger the container's custom event callback function.
	function triggerEvent(targets, $container, callback, detail) {
		if ($container[0] !== window && !$container.is(":visible") && $.isFunction(callback)) {
			return;
		}

		var $elements;
		if (isString(targets)) {
			targets = $.trim(targets);
			$elements = targets ? ($container[0] === window ? $(targets) : $(targets, $container)): [];
		}
		else {
			$elements = targets;
		}

		// filter the elements which are inside the view port
		var elements = filterInsideView($elements, $container[0]);

		// calculate viewport detail infos
		var $patchContainer = $(patchContainer($container[0])),
			scrollTop  = $patchContainer.scrollTop(),
			scrollLeft = $patchContainer.scrollLeft(),
			scrollHeight = parseInt($patchContainer[0].scrollHeight),
			scrollWidth  = parseInt($patchContainer[0].scrollWidth),
			thresholdBorderReachingVert = percent ? parseInt(scrollHeight * opts.thresholdBorderReaching) :
											 opts.thresholdBorderReaching,
			thresholdBorderReachingHor  = percent ? parseInt(scrollWidth * opts.thresholdBorderReaching) :
											 opts.thresholdBorderReaching;
			paddingTop = parseInt($patchContainer.css("padding-top")) || 0;
			paddingBottom = parseInt($patchContainer.css("padding-bottom")) || 0;
			paddingLeft = parseInt($patchContainer.css("padding-left")) || 0;
			paddingRight = parseInt($patchContainer.css("padding-right")) || 0;

		detail.top = scrollTop - thresholdBorderReachingVert <= 0;
		detail.left = scrollLeft - thresholdBorderReachingHor <= 0;
		detail.bottom = paddingTop + paddingBottom + $container.height() + scrollTop + thresholdBorderReachingVert >= scrollHeight;
		detail.right = paddingLeft + paddingRight + $container.width() + scrollLeft + thresholdBorderReachingHor >= scrollWidth;

		// do callback with the "onViewChange" custom event.
		callback.call($container, elements, detail);
	}

	// initialize each container and bind events.
	var timer;
	this.each(function() {
		var $container = $(this);
		var scrollTop = $container.scrollTop();
		var scrollLeft = $container.scrollLeft();

		// delay trigger the scroll or resize event.
		$container.bind("scroll resize", function(event) {
			window.clearTimeout(timer);
			timer = window.setTimeout(function() {
				var oldScrollTop = scrollTop;
				var oldScrollLeft = scrollLeft;
				scrollTop = $container.scrollTop();
				scrollLeft = $container.scrollLeft();

				var detail = {
						vert: scrollTop - oldScrollTop,
						hor: scrollLeft - oldScrollLeft
					};
				triggerEvent(opts.targets, $container, opts.onViewChange, detail);
			}, opts.delay);
		})
		// custom event "viewchange" for event trigger
		.bind("viewchange", function(event) {
			triggerEvent(opts.targets, $container, opts.onViewChange, {vert: 0, hor: 0});
		});

		// trigger the container's custom event immediately.
		triggerEvent(opts.targets, $container, opts.onViewChange, {vert: 0, hor: 0});
	});

	return this;
};
})(jQuery);